Eng_Lab 02 - ROS2 Actions
Lab. 02 - ROS2 Actions

1. What are ROS2 Actions?
Actions are one of the communication types in ROS2, meant for tasks that take longer. They consist of three parts: a goal, feedback and a result.
Actions are built on topics and services. They work similarly to services, with two differences:
- they can be stopped before they finish,
- they provide continuous feedback while running, whereas a service reports the outcome only once, at the end of execution.
Goal - used to call the action. It works like a service: a call and a response saying whether the action was started.
Feedback - data published by the action server while it runs. It usually reports the current progress, e.g. the distance left to travel, the estimated remaining time or the percentage of the work done.
Result - the final response of the action server about the outcome. It usually contains a binary answer saying whether the requested task succeeded, or how accurately the action was carried out.
2. Environment setup
Before you start, prepare the environment: Environment
setup instructions. The image is based on
osrf/ros:lyrical-desktop-full (ROS 2 Lyrical Luth).
By default the container is named ARM_02.
NOTE! When run, the scripts remove a container with this name before creating a new one.
Using the container
After every computer restart, remember to run:
xhost +local:root
A new terminal can be attached to the container with:
docker exec -it ARM_02 bash
ROS_DOMAIN_ID
When several computers work in the same local network
(eduroam may require this too), you may need to set the
ROS_DOMAIN_ID environment variable to different values.
Choose any integer from 0 to 100. Set this value in the
~/.bashrc file:
export ROS_DOMAIN_ID=10 # example value
or directly in the terminal (it then applies only to that terminal):
export ROS_DOMAIN_ID=10 # example value
It is suggested to use the last two digits of the computer’s IP
address as the ROS_DOMAIN_ID value, to avoid conflicts with
other users in the network. To check your IP address, you can use:
ip addr show
The IP address is shown under the network interface,
e.g. wlan0 or eth0.
It may be convenient to add this export to bashrc:
echo 'export ROS_DOMAIN_ID=<chosen_value>' >> ~/.bashrc
Gazebo: GZ_IP and GZ_PARTITION
Gazebo communicates independently of ROS 2 (through the
gz-transport library), so ROS_DOMAIN_ID does not
separate simulations. By default, Gazebo connects simulations started by
the same user on computers with the same name (hostname), and a
container started with --network=host takes the computer’s
name and runs as root. If computers in the network have the
same names, simulations of different people can interfere with each
other.
That is why the image sets GZ_IP=127.0.0.1 in the
~/.bashrc file, so Gazebo communicates only within one
computer. In containers created from an older version of the image (the
echo $GZ_IP command prints nothing), add it yourself:
echo 'export GZ_IP=127.0.0.1' >> ~/.bashrc
If several simulations run on one computer (e.g. in several
containers), you can also separate them by setting a different
GZ_PARTITION value in each container (Gazebo nodes only see
each other within the same partition):
echo 'export GZ_PARTITION=<chosen_name>' >> ~/.bashrc # e.g. arm_10
The GZ_PARTITION value must be the same in all terminals
where you use Gazebo (starting the simulation, gz
commands), so it is better to set it in ~/.bashrc than in a
single terminal. Changes in ~/.bashrc apply to newly opened
terminals, so restart the simulation after making them.
3. Goal of the class
The final task of this class is to create an action that moves the robot to a given distance from the obstacle in front of it, also passing the time allowed for the task and the minimum required accuracy. The feedback will publish the current distance to the obstacle and the estimated time left until the action finishes. As the result, the server will report whether the requirements were met, the final accuracy and distance to the goal, and how long the procedure took.
4. Creating an action
Before working on the task above, it is worth learning how to define actions on a simpler example - computing the Fibonacci sequence.
Creating a new package
To create a new package, run the following command from
/arm_ws/src:
ros2 pkg create --build-type ament_cmake fibonacci_sequence_action
You also need to create an action directory and define
the action structure in it:
cd /arm_ws/src/fibonacci_sequence_action
mkdir action && cd action
touch Fibonacci.action
Edit the Fibonacci.action file (e.g. with
nano, gedit or VS code):
int32 order
---
int32[] sequence
---
int32[] partial_sequence
where order is the goal of the action, sequence is the result and partial_sequence is the feedback.
With the action structure defined, we can add it to rosidl,
the ROS2 pipeline that generates the interface code. To do this, modify
the CMakeLists.txt file (before the
ament_package() line) so that it contains:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"action/Fibonacci.action"
)Also add the required dependencies to the package.xml
file:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>At this point it should be possible to build the action definition:
cd /arm_ws
source /opt/ros/lyrical/setup.bash
colcon build --symlink-install
If the build succeeded, you can view the action definition through the ROS2 interface tools:
source /arm_ws/install/setup.bash
ros2 interface show fibonacci_sequence_action/action/Fibonacci
Preparing the action server script
Now that the new package builds and ROS2 provides the action structure, we can start writing the action server script.
Start by creating a scripts folder and a
fibonacci_action_server.py file:
cd /arm_ws/src/fibonacci_sequence_action/
mkdir scripts && cd scripts
touch fibonacci_action_server.py
The fibonacci_action_server.py file should look as
follows:
#!/usr/bin/env python3
import time
import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
from fibonacci_sequence_action.action import Fibonacci
class FibonacciActionServer(Node):
def __init__(self):
super().__init__('fibonacci_action_server')
self._action_server = ActionServer(
self,
Fibonacci,
'fibonacci',
self.execute_callback)
self.get_logger().info('Fibonacci Action Server is running...')
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')
feedback_msg = Fibonacci.Feedback()
feedback_msg.partial_sequence = [0, 1]
for i in range(1, goal_handle.request.order):
feedback_msg.partial_sequence.append(
feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])
self.get_logger().info('Feedback: {0}'.format(feedback_msg.partial_sequence))
goal_handle.publish_feedback(feedback_msg)
time.sleep(1)
goal_handle.succeed()
result = Fibonacci.Result()
result.sequence = feedback_msg.partial_sequence
return result
def main(args=None):
rclpy.init(args=args)
fibonacci_action_server = FibonacciActionServer()
rclpy.spin(fibonacci_action_server)
if __name__ == '__main__':
main()The file should also be marked as executable:
chmod +x fibonacci_action_server.py
You also need to add the
scripts/fibonacci_action_server.py path and the
rclpy and ament_cmake_python packages to the
CMakeLists.txt file:
find_package(ament_cmake_python REQUIRED)
find_package(rclpy REQUIRED)
install(PROGRAMS
scripts/fibonacci_action_server.py
DESTINATION lib/${PROJECT_NAME}
)and the corresponding dependencies to the package.xml
file:
<buildtool_depend>ament_cmake_python</buildtool_depend>
<depend>rclpy</depend>The action server is done. You can now build the package and run it:
cd /arm_ws
colcon build --symlink-install
source install/setup.bash
ros2 run fibonacci_sequence_action fibonacci_action_server.py
In a separate terminal you can check the list of available actions:
ros2 action list
and send a goal to the action:
ros2 action send_goal --feedback /fibonacci fibonacci_sequence_action/action/Fibonacci "{order: 10}"
If you get an error about a missing executable, remove the
build,installandlogfolders and rebuild the workspace:rm -r /arm_ws/build /arm_ws/install /arm_ws/log source /opt/ros/lyrical/setup.bash colcon build --symlink-install
5. Independent task
Your task is to prepare an action that meets the requirements from section 3. Use the simulation for this:
cd /arm_ws
source install/setup.bash
ros2 launch arm02 arm02_world.launch.pyIt provides two topics:
- /laser_scan (sensor_msgs/msg/LaserScan)
- /cmd_vel (geometry_msgs/msg/Twist)
The first one gives the distance to the obstacle, the second one should be used for motion (linear, along the x axis).
Help with writing subscribers in ROS2 can be found here.
The action should be defined as below:
float32 goal_distance
float32 timeout
float32 precision
---
bool succeeded
float32 final_precision
float32 total_time
---
float32 current_distance
float32 estimated_time
NOTE! The action file name must start with a capital letter and contain only letters and digits.
Hint: how to subscribe to a topic and execute an action at the same time?
Putting a loop in the method that executes the action will make the program stop reading messages from the /laser_scan topic.
To deal with this, you can use a multithreaded executor (
MultiThreadedExecutor) instead of the default one provided by rclpy, and assign the subscriber and the action server to aReentrantCallbackGroup. Messages from the topic will then be received while the action is running. More information and an example can be found here and here.NOTE! Calling
rclpy.spin_once(self)inside the method that executes the action, as recommended by older tutorials, fails in ROS 2 Lyrical with theExecutor is already spinningerror.
Upload to eKursy:
- the action server script (please change its extension to .txt)
- a screenshot of the terminal with the feedback visible
- screenshots of the terminal with the result visible (when the timeout was exceeded and when the action succeeded)
Author: Kamil Młodzikowski
Based on: Creating
an action and Writing
an action server and client (Python) from docs.ros.org.